package com.zkh.myutils.utils;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.StringJoiner;
import java.util.UUID;

import com.zkh.myutils.code.Charsets;
import com.zkh.myutils.io.IOUtils;
import com.zkh.myutils.io.log.Logger;
import com.zkh.myutils.utils.enums.EncryptType;


public final class Utils {
	
	//备用字符，用来生成短8位UUID
	private static String[] chars = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("");
	
	/**
	 * 拼接成字符串
	 * @param separator 分割符
	 * @param elements 拼接元素
	 */
	@SafeVarargs
	public static <T> String join(String separator, T... elements) {
        return join(separator, Arrays.asList(elements));
	}
	
	/**
	 * 拼接成字符串
	 * @param separator 分割符
	 * @param elements 拼接元素
	 */
	public static String join(String separator, Iterable<? extends Object> elements) {
		Objects.requireNonNull(separator);
        Objects.requireNonNull(elements);
        StringJoiner joiner = new StringJoiner(separator);
        for (Object cs: elements) {
            joiner.add(cs==null ? "" : cs.toString());
        }
        return joiner.toString();
	}
	
	/**
	 * 获取UUID（32位）
	 * @return UUID值
	 */
	public static String getUUID(){
		return UUID.randomUUID().toString().replace("-","");
	}
	
	/**
	 * 获取短8位UUID（8位）
	 */
	public static String getShortUUID() {
		return reduce(getUUID());
	}
		
	/**
	 * 对字符串进行md5加密
	 * @param oldStr 要加密的字符串
	 * @return 加密后的字符串
	 */
	public static String md5(String oldStr){
		return encrypt(oldStr, EncryptType.MD5);
	}
	
	/**
	 * 获取短8位MD5（8位）
	 */
	public static String md5Short(String oldStr) {
		return reduce(md5(oldStr));
	}
	
	/**
	 * 缩短字符串，主要用于UUID和MD5串
	 */
	private static String reduce(String var) {
		//字符串
		StringBuilder shortBuilder = new StringBuilder();
		//循环出8位字符
		for(int i=0; i<8; i++){
			//截取字符
			String str = var.substring(i*4, i*4+4);
			//转16进制数字
			int x = Integer.parseInt(str, 16);
			//生成字符
			shortBuilder.append(chars[x % 0x3E]);
		}
		//返回
		return shortBuilder.toString();
	}
	
	/**
	 * 计算文件的加密值
	 * @param file 指定文件
	 * @param type 加密算法类型。通过枚举类“EncryptType”获取
	 */
	public static String encrypt(File file, EncryptType type){
		//读取文件
		try(InputStream is = new FileInputStream(file)){
			return encrypt(IOUtils.toByteArray(is), type);
		}catch (Exception e) {
			throw new UtilsException(e);
		}
	}
	
	/**
	 * 对字符串进行加密
	 * @param oldStr 要加密的字符串
	 * @param type 加密类型。通过枚举类“EncryptType”获取
	 */
	public static String encrypt(String oldStr, EncryptType type){
		//检查为空
		if(StringUtils.isEmpty(oldStr)){
			return null;
		}
		//检查类型
		if(type==null) {
			throw new UtilsException("加密类型不能为空");
		}
		return encrypt(oldStr.getBytes(Charsets.UTF_8), type);
	}
	
	/**
	 * 对字符串进行加密
	 * @param data 要加密的数据
	 * @param type 加密类型。通过枚举类“EncryptType”获取
	 */
	public static String encrypt(byte[] data, EncryptType type){
		Objects.requireNonNull(data);
		//检查类型
		if(type==null) {
			throw new UtilsException("加密类型不能为空");
		}
		try {
			//加密后字符串
			StringBuilder encryptString = new StringBuilder();
			//确定计算方法(传入加密类型)
			MessageDigest encrypt = MessageDigest.getInstance(type.name);
			//获取加密后的字节数组
			byte[] bytes = encrypt.digest(data);
			//将encodeMd5Digest转换为16进制的字符串
			for(byte b : bytes){
				encryptString.append(String.format("%02x", b));
			}
			//返回加密字符串
			return encryptString.toString();
		} catch (NoSuchAlgorithmException e) {
			throw new UtilsException(e.getMessage());
		}
	}
	
	/**
	 * 获取一个随机数
	 * @param max 最大范围
	 * @return 0到max之间的随机整数，区间为 [0,max)
	 */
	public static int rand(int max){
		return rand(0, max);
	}
	
	/**
	 * 获取一个随机数
	 * @param min 最小值
	 * @param max 最大范围
	 * @return min到max之间的随机整数，区间为 [min,max)
	 */
	public static int rand(int min,int max){
		//获取[min,max)之间的随机小数
		double random = Math.random() * (max - min) + min;
		//类型转换
		return (int)random;
	}
	
	/**
	 * 获取从0到max（包含max）的随机乱序值
	 */
	public static int[] randArray(int max) {
		//长度
		int len = max + 1;
		//初始容器
		int[] arr = new int[len];
		//保存已存在的值
		List<Integer> exists = new ArrayList<Integer>();
		//遍历赋值
		for(int i=0;i<len;i++) {
			//获取一个随机值
			int rand = rand(0, len);
			//如果包含已经取得的值，则重新取值
			while(exists.contains(rand)) {
				rand = rand(0, len);
			}
			//保存值
			arr[i] = rand;
			exists.add(rand);
		}
		return arr;
	}
	
	/**
	 * 获取一个有初始值的HashMap
	 * @param map Map容器，用来装载数据
	 * @param keyValues 键值对依次排列的数据。如：one, 1, two, 2, three, 3 这组数据，会将one, two, three置为key，value分别为1, 2, 3。如果参数长度为奇数，最后一个（key）直接忽略。
	 */
	@SuppressWarnings("unchecked")
	public static <K, V> Map<K, V> getMap(Map<K, V> map, Object... keyValues) {
		//判断参数
		if(keyValues!=null && keyValues.length>1) {
			//遍历
			for(int i=1,len=keyValues.length; i<len; i=i+2) {
				map.put((K) keyValues[i-1], (V) keyValues[i]);
			}
		}
		//返回
		return map;
	}
	
	/**
	 * 获取一个有初始值的HashMap
	 * @param map Map容器，用来装载数据
	 * @param keyValues 键值对依次排列的数据。如：[one, 1], [two, 2] 这组数据，会将one, two置为key，value分别为1, 2。如果数组长度小于2，则忽略；如果大于2，索引0为key，1为value，其余均忽略。
	 */
	public static <K, V> Map<K, V> getMap(Map<K, V> map, Object[]... keyValues) {
		//参数容器
		List<Object> ctn = new ArrayList<Object>();
		//遍历
		if(keyValues!=null && keyValues.length>0) {
			for(Object[] kv: keyValues) {
				//至少2个元素才保存
				if(kv!=null && kv.length>=2) {
					ctn.add(kv[0]);ctn.add(kv[1]);
				}
			}
		}
		//返回
		return getMap(map, ctn);
	}
	
	/**
	 * 转换字符串编码
	 * @param target 要转换的字符串
	 * @param from 字符原来的编码。可通过常量“Charsets.编码”获取
	 * @param to 新的编码。可通过常量“Charsets.编码”获取
	 * @return 转换编码后的新字符串
	 */
	public static String convertEncode(String target, Charset from, Charset to){
		//如果字符串为null，则返回null
		if(target==null){
			return null;
		}
		return new String(target.getBytes(from), to);
	}
	
	/**
	 * 判断为空，同时判断空字符串、空集合、空Map和空数组
	 * @param value 指定值
	 */
	public static boolean isEmpty(Object value) {
		return value==null //对象为空
				|| (value instanceof String && ((String) value).isEmpty())	//字符串为空
				|| (value instanceof Map && ((Map<?, ?>) value).isEmpty())	//Map为空
				|| (value instanceof Collection && ((Collection<?>) value).isEmpty())	//Collection为空
				|| (value.getClass().isArray() && Array.getLength(value)==0);	//数组为空
	}
	
	/**
	 * 将异常对象转化为字符串
	 * @param throwable 异常对象
	 */
	public static String getStackTrace(Throwable throwable) {
		//创建写对象
		StringWriter sw = new StringWriter();
		PrintWriter pw = new PrintWriter(sw, true);
		//打印异常信息到写对象
		throwable.printStackTrace(pw);
		//返回异常信息
		return sw.getBuffer().toString();
	}

	/**
	 * 解析变量值
	 * @param key 变量字符串
	 * @param params 数据容器
	 */
	@SuppressWarnings("rawtypes")
	public static Object parseValue(String key, Map<String, ? extends Object> params) {
		//转字符
		char[] keyChars = key.toCharArray();
		//字符长度
		int len = keyChars.length;
		//临时值
		Object tempValue = params;
		//临时字符
		StringBuilder sb = new StringBuilder();
		//遍历
		for(int i=0; i<len; i++) {
			//当前字符
			char curr = keyChars[i];
			//如果遇到点或中括号
			if(curr=='.' || curr=='[') {
				//检查空
				Objects.requireNonNull(tempValue);
				//获取值
				tempValue = tempValue instanceof Map ? ((Map) tempValue).get(sb.toString()) : getBeanValue(tempValue, sb.toString());
				//清空字符
				sb.setLength(0);
				//左括号，继续解析右括号
				if(curr=='[') {
					while(true) {
						//计数
						int count = 1;
						//下一字符
						char next = i+1>=len ? '\0' : keyChars[i+1];
						//不为右括号，一直解析
						while(next!='\0' && (next!=']' || count>0)) {
							//保存
							sb.append(next);
							//移动指针
							i++;
							//下一字符
							next = i+1>=len ? '\0' : keyChars[i+1];
							//为左括号，计数加1
							if(next=='[') {
								count++;
							}//为右括号，计数减1
							else if(next==']'){
								count--;
							}
						}
						//结束
						if(next=='\0') {
							throw new IllegalArgumentException("变量解析失败：" + key);
						}
						//获取字符
						String str = sb.toString();
						//清空
						sb.setLength(0);
						//首字符
						char first = str.charAt(0);
						//如果是数字
						if(str.matches("^\\d+$")) {
							//获取数组数据
							tempValue = getListValue(tempValue, Integer.parseInt(str));
						}//引号
						else if(first=='\'' || first=='"') {
							//去掉引号
							str = str.substring(1, str.length()-1);
							//获取值
							tempValue = tempValue instanceof Map ? ((Map) tempValue).get(str) : getBeanValue(tempValue, str);
						}//其它统一默认为变量
						else {
							//解析key
							Object k = parseValue(str, params);
							//解析值
							if(k instanceof Number) {
								//获取值
								tempValue = getListValue(tempValue, ((Number) k).intValue());
							}else {
								//获取值
								tempValue = tempValue instanceof Map ? ((Map) tempValue).get(k.toString()) : getBeanValue(tempValue, k.toString());
							}
						}
						//移动指针，跳过右括号
						i++;
						//下一字符
						next = i+1>=len ? '\0' : keyChars[i+1];
						//移动指针，不管下个字符是点还是括号，都要跳过
						i++;
						//如果是点，跳过后继续解析
						if(next=='.' || next=='\0') {
							//跳出循环
							break;
						}//右括号后面，要么是点，要么再跟左括号
						else if(next!='[') {
							throw new IllegalArgumentException("变量解析失败：" + key);
						}
					}
				}
			}//其它，继续解析
			else {
				//保存当前字符
				sb.append(curr);
			}
		}
		if(sb.length()>0) {
			//检查空
			Objects.requireNonNull(tempValue);
			//如果有字符，表示不是以右括号结尾，需要最后解析一次
			tempValue = tempValue instanceof Map ? ((Map) tempValue).get(sb.toString()) : getBeanValue(tempValue, sb.toString());
		}
		//返回值
		return tempValue;
	}

	/**
	 * 获取数组或集合值
	 * @param value 数组或集合
	 * @param index 索引
	 */
	public static Object getListValue(Object value, int index) {
		//value为null时，自动抛出空指针异常，不用刻意判断value为null的情况
		if(value.getClass().isArray()) {
			return Array.get(value, index);
		}//List集合
		else if(value instanceof List) {
			return ((List<?>) value).get(index);
		}//其它集合
		else if(value instanceof Collection) {
			//计数
			int count = 0;
			//遍历集合
			for(Object v: (Collection<?>) value) {
				if(count++ ==index) {
					return v;
				}
			}
			//未获取到值，说明索引越界了
			throw new IndexOutOfBoundsException();
		}else {
			throw new IllegalArgumentException(value + "未实现Collection或不是数组");
		}
	}

	/**
	 * 获取bean属性值
	 * @param bean Bean
	 * @param attrName 属性名
	 */
	public static Object getBeanValue(Object bean, String attrName) {
		//getter
		try {
			return bean.getClass().getMethod("get" + StringUtils.uppercaseFirst(attrName)).invoke(bean);
		} catch (NoSuchMethodException e) {
			throw new IllegalArgumentException("属性" + attrName + "没有对应的getter，获取值失败");
		} catch(IllegalAccessException | IllegalArgumentException | InvocationTargetException | SecurityException e) {
			//输出日志
			Logger.error(e);
			//抛出异常
			throw new IllegalArgumentException("属性" + attrName + "调用getter失败");
		}
	}
}
